#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

#include <psp2/io/dirent.h>
#include <psp2/io/fcntl.h>
#include <psp2/io/stat.h>

#include "utils.h"

int makeBaseDirectoryEx(char *base, int base_size, const char *path, int path_len)
{
    if (!base || base_size <= 0)
        return -1;
    base[0] = '\0';

    if (!path || path_len <= 0)
        return -1;

    if ((path_len == 1 && path[0] == '/') || path[path_len - 1] == ':')
    {
        if (base_size > path_len)
        {
            strncpy(base, path, path_len);
            base[path_len] = '\0';
        }
        return -1;
    }

    if (path[path_len - 1] == '/')
        path_len--;

    int sep_idx = -1;

    int i;
    for (i = path_len - 1; i >= 0; i--)
    {
        if (path[i] == '/' || path[i] == ':')
        {
            sep_idx = i;
            break;
        }
    }
    if (sep_idx == -1)
        return -1;

    int new_len = sep_idx + 1;
    if (new_len > base_size - 1)
        new_len = base_size - 1;
    if (new_len > 0)
        strncpy(base, path, new_len);
    base[new_len] = '\0';

    return 0;
}

int makeFileNameEx(char *name, int name_size, const char *path, int path_len)
{
    if (!name || name_size <= 0)
        return -1;
    name[0] = '\0';

    if (!path || path_len <= 0)
        return -1;

    if (path[path_len - 1] == '/' || path[path_len - 1] == ':')
        return -1;

    int sep_idx = -1;

    int i;
    for (i = path_len - 1; i >= 0; i--)
    {
        if (path[i] == '/' || path[i] == ':')
        {
            sep_idx = i;
            break;
        }
    }
    if (sep_idx == -1)
        return -1;

    int new_len = path_len - (sep_idx + 1);
    if (new_len > name_size - 1)
        new_len = name_size - 1;
    if (new_len > 0)
        strncpy(name, path + (sep_idx + 1), new_len);
    name[new_len] = '\0';

    return 0;
}

int makeBaseNameEx(char *name, int name_size, const char *path, int path_len)
{
    if (!name || name_size <= 0)
        return -1;
    name[0] = '\0';

    if (!path || path_len <= 0)
        return -1;

    int sep_idx1 = -1;
    int sep_idx2 = path_len;

    int i;
    for (i = path_len - 1; i >= 0; i--)
    {
        if (path[i] == '/' || path[i] == ':')
        {
            sep_idx1 = i;
            break;
        }
        else if (path[i] == '.' && sep_idx2 == path_len)
        {
            sep_idx2 = i;
        }
    }

    int new_len = sep_idx2 - (sep_idx1 + 1);
    if (new_len > name_size - 1)
        new_len = name_size - 1;
    if (new_len > 0)
        strncpy(name, path + (sep_idx1 + 1), new_len);
    name[new_len] = '\0';

    return 0;
}

int makeBaseDirectory(char *base_dir, const char *path, int size)
{
    return makeBaseDirectoryEx(base_dir, size, path, strlen(path));
}

int makeFilename(char *name, const char *path, int size)
{
    return makeFileNameEx(name, size, path, strlen(path));
}

int makeBaseName(char *name, const char *path, int size)
{
    return makeBaseNameEx(name, size, path, strlen(path));
}

int checkFileExist(const char *file)
{
    SceUID fd = sceIoOpen(file, SCE_O_RDONLY, 0);
    if (fd < 0)
        return 0;

    sceIoClose(fd);
    return 1;
}

int checkFolderExist(const char *folder)
{
    SceUID dfd = sceIoDopen(folder);
    if (dfd < 0)
        return 0;

    sceIoDclose(dfd);
    return 1;
}

int getFileSize(const char *file)
{
    SceUID fd = sceIoOpen(file, SCE_O_RDONLY, 0);
    if (fd < 0)
        return fd;

    int fileSize = sceIoLseek(fd, 0, SCE_SEEK_END);

    sceIoClose(fd);
    return fileSize;
}

int ReadFile(const char *file, void *buf, int size)
{
    SceUID fd = sceIoOpen(file, SCE_O_RDONLY, 0);
    if (fd < 0)
        return fd;

    int read = sceIoRead(fd, buf, size);

    sceIoClose(fd);
    return read;
}

int WriteFile(const char *file, const void *buf, int size)
{
    SceUID fd = sceIoOpen(file, SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC, 0777);
    if (fd < 0)
        return fd;

    int written = sceIoWrite(fd, buf, size);

    sceIoClose(fd);
    return written;
}

int allocateReadFile(const char *file, void **buffer)
{
    SceUID fd = sceIoOpen(file, SCE_O_RDONLY, 0);
    if (fd < 0)
        return fd;

    int size = sceIoLseek32(fd, 0, SCE_SEEK_END);
    sceIoLseek32(fd, 0, SCE_SEEK_SET);

    *buffer = malloc(size);
    if (!*buffer)
    {
        sceIoClose(fd);
        return -1;
    }

    int read = sceIoRead(fd, *buffer, size);
    sceIoClose(fd);

    return read;
}

int removeFile(const char *path)
{
    SceUID dfd = sceIoDopen(path);
    if (dfd >= 0)
    {
        int res = 0;

        do
        {
            SceIoDirent dir;
            memset(&dir, 0, sizeof(SceIoDirent));

            res = sceIoDread(dfd, &dir);
            if (res > 0)
            {
                char new_path[256];
                snprintf(new_path, sizeof(new_path), "%s/%s", path, dir.d_name);
                res = removeFile(new_path);
            }
        } while (res > 0);

        sceIoDclose(dfd);

        return sceIoRmdir(path);
    }

    return sceIoRemove(path);
}

int copyFile(char *src_path, char *dst_path)
{
    int src_len = strnlen(src_path, MAX_PATH_LENGTH);
    int dst_len = strnlen(dst_path, MAX_PATH_LENGTH);

    // The source and destination paths are identical
    if (src_len == dst_len && strncasecmp(src_path, dst_path, src_len) == 0)
    {
        return -1;
    }

    // The destination is a subfolder of the source folder
    if (strncasecmp(src_path, dst_path, src_len) == 0 && dst_path[src_len] == '/')
    {
        return -1;
    }

    SceUID fdsrc = sceIoOpen(src_path, SCE_O_RDONLY, 0);
    if (fdsrc < 0)
        return fdsrc;

    SceUID fddst = sceIoOpen(dst_path, SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC, 0777);
    if (fddst < 0)
    {
        sceIoClose(fdsrc);
        return fddst;
    }

    void *buf = malloc(TRANSFER_SIZE);
    if (!buf)
    {
        sceIoClose(fddst);
        sceIoClose(fdsrc);
        return -1;
    }

    int read;
    while ((read = sceIoRead(fdsrc, buf, TRANSFER_SIZE)) > 0)
    {
        int res = sceIoWrite(fddst, buf, read);
        if (res < 0)
        {
            free(buf);

            sceIoClose(fddst);
            sceIoClose(fdsrc);

            return res;
        }
    }

    free(buf);

    sceIoClose(fddst);
    sceIoClose(fdsrc);

    return 1;
}
